package com.basker.pisces.domain.validation.validator;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.basker.pisces.concurrent.collection.IConcurrentCollectionManager;
import com.basker.pisces.core.RowContract;
import com.basker.pisces.core.meta.IDataField;
import com.basker.pisces.core.meta.IDataObjectMeta;
import com.basker.pisces.core.query.QueryInfo;
import com.basker.pisces.core.query.builder.CriterionBuilder;
import com.basker.pisces.core.query.builder.QueryBuilder;
import com.basker.pisces.core.query.meta.QueryMeta;
import com.basker.pisces.core.repository.IDataRepository;
import com.basker.pisces.domain.command.CommandExecuteContext;
import com.basker.pisces.domain.command.event.CommandExecuteEvent;
import com.basker.pisces.domain.command.processor.ICommandExecuteProcessor;
import com.basker.pisces.domain.entity.meta.Entity;
import com.basker.pisces.domain.property.meta.Property;
import com.basker.pisces.domain.validation.ValidationErrors;
import com.basker.pisces.domain.validation.annotation.CommandValidator;
import com.basker.pisces.domain.validation.meta.UniquePropertyValidation;
import com.basker.pisces.domain.value.IValueObject;
import com.basker.pisces.service.exception.IObjectErrorsWrapper.ObjectError;
import com.basker.pisces.tx.TransactionHelper;
import com.basker.pisces.utils.PageUtils;

@CommandValidator
public class UniquePropertyValidator extends AbstractConfigValidator<UniquePropertyValidation>
        implements ICommandExecuteProcessor {

    private static final String UNIQUE_VALUE_SET_KEY = "$" + UniquePropertyValidator.class.getSimpleName()
            + "$VALUESET";

    @Autowired
    private IConcurrentCollectionManager collectionManager;

    /**
     * uniqueKeySet是一个支持并发控制的集合
     */
    private Set<String> uniqueValueSet;

    @Autowired
    private IDataRepository repository;

    @Override
    public List<String> getRequiredPropertyAliasList() {
        return this.getValidation().getUniquePropertyNames();
    }

    @Override
    public void processAfterInvoke(CommandExecuteEvent event) {
        // 如果在事务内，那么须在事务完成后再释放集合，否则唯一性数据库层的校验可能会失效
        if (TransactionHelper.inTransactionScope()) {
            TransactionHelper.doAfterCompletion(status -> this.clearContextUniqueValues());
        } else {
            this.clearContextUniqueValues();
        }
    }

    @Override
    public void setContext(CommandExecuteContext context) {
        super.setContext(context);
        this.createUniqueValueSet();
    }

    @Override
    public ValidationErrors validate(Object[] dataObjects) {
        UniquePropertyValidation validation = this.getValidation();
        List<String> uniquePropertyNames = validation.getUniquePropertyNames();

        // 没有配置唯一字段，忽略
        if (CollectionUtils.isEmpty(uniquePropertyNames)) {
            return null;
        }

        ValidationErrors errors = new ValidationErrors();
        List<Property<?>> uniqueProperties = this.getUniqueProperties(uniquePropertyNames);

        // 校验需要考虑新增对象，和唯一属性有过更改的更新对象
        Object[] resolvedDataObjects = this.getResolvedDataObjects(uniqueProperties, dataObjects);
        if (resolvedDataObjects.length == 0) {
            return null;
        }

        // 1.对传入的数据按唯一值分组
        UniqueValueGroup uniqueValueGroup = this.buildUniqueValueGroup(uniqueProperties, resolvedDataObjects, errors);
        if (CollectionUtils.isEmpty(uniqueValueGroup)) {
            return null;
        }

        resolvedDataObjects = errors.filterValidObjects(Arrays.asList(resolvedDataObjects)).toArray();

        // 2.并发校验
        if (resolvedDataObjects.length > 0) {
            this.validateConcurrent(uniqueProperties, resolvedDataObjects, uniqueValueGroup, errors);
            resolvedDataObjects = errors.filterValidObjects(Arrays.asList(resolvedDataObjects)).toArray();
        }

        // 3.与数据库中的数据对比,提出并发校验错误的对象
        if (resolvedDataObjects.length > 0) {
            this.validateExist(uniqueProperties, resolvedDataObjects, uniqueValueGroup, errors);
        }

        return errors;
    }

    private QueryInfo buildQueryInfoForBatch(QueryMeta queryMeta, Property<?> property, Object[] dataObjects) {
        QueryBuilder queryBuilder = QueryBuilder.create(queryMeta).select(property.getAlias());

        List<Object> uniqueValues = new ArrayList<>(dataObjects.length);
        for (Object dataObject : dataObjects) {
            Object value = this.getUniqueValue(property, dataObject);
            if (value == null) {
                continue;
            }

            uniqueValues.add(value);
        }

        if (!CollectionUtils.isEmpty(uniqueValues)) {
            queryBuilder.andCriteria(CriterionBuilder.in(property.getAlias(), uniqueValues.toArray()));
        }

        return queryBuilder.buildQueryInfo();
    }

    private QueryInfo buildQueryInfoForCount(QueryMeta queryMeta, List<Property<?>> properties, Object dataObject) {
        QueryBuilder queryBuilder = QueryBuilder.create(queryMeta)
                .select(queryMeta.getMainTable().getPkField().getId());

        for (Property<?> property : properties) {
            Object value = this.getUniqueValue(property, dataObject);
            if (value == null) {
                continue;
            }

            queryBuilder.andCriteria(CriterionBuilder.equal(property.getAlias(), value));
        }

        return queryBuilder.buildQueryInfo();
    }

    /**
     * 将对象按唯一值分组，若分组里元素数量>1，说明当前批次有重复值
     *
     * @return
     */
    private UniqueValueGroup buildUniqueValueGroup(List<Property<?>> uniqueProperties, Object[] dataObjects,
                                                   ValidationErrors errors) {
        UniqueValueGroup uniqueValueGroup = new UniqueValueGroup();

        Stream.of(dataObjects).forEach(o -> {
            String value = this.getDataObjectUniqueValue(uniqueProperties, o);

            if (value != null) {
                List<Object> list = uniqueValueGroup.get(value);
                if (list == null) {
                    list = new ArrayList<>();
                    uniqueValueGroup.put(value, list);
                }

                list.add(o);
            }
        });

        // 如果存在某一组的元素数量大于1，那么创建一个错误
        for (Entry<String, List<Object>> entry : uniqueValueGroup.entrySet()) {
            List<Object> group = entry.getValue();
            if (group.size() > 1) {
                UniquePropertyValidation validation = this.getValidation();

                for (Object dataObject : group) {
                    ObjectError createError = validation.createUniquePropertyMultipleError(dataObject,
                            uniqueProperties);

                    errors.addError(createError);
                }
            }
        }

        return uniqueValueGroup;
    }

    /**
     * 从全局并发集合里删除当前上下文的唯一值
     */
    private void clearContextUniqueValues() {
        String key = genValueSetKey();

        @SuppressWarnings("unchecked")
        Set<String> tempSet = (Set<String>) this.getContext().getParameter(key);

        if (tempSet != null) {
            uniqueValueSet.removeAll(tempSet);
            this.getContext().removeParameter(key);
        }
    }

    private void createUniqueValueSet() {
        this.uniqueValueSet = this.collectionManager.getSet(genValueSetKey());
    }

    private String genValueSetKey() {
        return UNIQUE_VALUE_SET_KEY + ":" + this.getEntity().getFullEntityName() + ":" + this.getValidation().getName();
    }

    private String getDataObjectUniqueValue(List<Property<?>> uniqueProperties, Object dataObject) {
        List<String> valueList = new ArrayList<>(uniqueProperties.size());

        boolean hasValue = false;

        for (Property<?> property : uniqueProperties) {
            Object value = this.getUniqueValue(property, dataObject);

            if (value == null) {
                continue;
            }

            valueList.add(String.valueOf(value));
            hasValue = true;
        }

        if (!hasValue) {
            return null;
        }

        return String.join(",", valueList.toArray(new String[valueList.size()]));
    }

    private Object[] getResolvedDataObjects(List<Property<?>> uniqueProperties, Object[] dataObjects) {
        IDataObjectMeta objectMeta = this.getEntity().getDataObjectMeta();
        return Stream.of(dataObjects).filter(o -> {
            boolean newObj = !objectMeta.isFromDB(o);
            if (newObj) {
                return true;
            }

            boolean uniqueValueChanged = uniqueProperties.stream().anyMatch(p -> {
                IDataField dataField = p.tryGetDataField();
                return dataField != null && objectMeta.isChanged(o, dataField);
            });

            return uniqueValueChanged;
        }).toArray(s -> new Object[s]);
    }

    private List<Property<?>> getUniqueProperties(List<String> uniquePropertyNames) {
        Entity entity = this.getEntity();
        return uniquePropertyNames.stream().map(name -> entity.getPropertyByAlias(name))
                .filter(p -> p != null && p.getHostEntity() == entity).collect(Collectors.toList());
    }

    private Object getUniqueValue(Property<?> property, Object dataObject) {
        IValueObject propertyValue = property.getValue(dataObject);
        return !IValueObject.isEmpty(propertyValue) ? propertyValue.getDataValue() : null;
    }

    private void validateConcurrent(List<Property<?>> uniqueProperties, Object[] dataObjects,
                                    UniqueValueGroup uniqueValueGroup, ValidationErrors errors) {
        // 并发校验，当前线程可能与其他线程都提交了相同唯一值的数据
        String[] uniqueValues = uniqueValueGroup.getUniqueValues();
        int len = uniqueValues.length;
        // 大于10个，并发处理
        boolean parallel = len > 10;
        Set<String> tempSet = parallel ? ConcurrentHashMap.newKeySet(len) : new HashSet<>(len, 1);

        AtomicBoolean hasError = new AtomicBoolean(false);

        try {
            if (parallel) {
                Stream.of(uniqueValues).parallel().forEach(uniqueValue -> {
                    this.validateConcurrent0(uniqueProperties, uniqueValue, tempSet, hasError, uniqueValueGroup, errors,
                            parallel);
                });
            } else {
                Stream.of(uniqueValues).forEach(uniqueValue -> {
                    this.validateConcurrent0(uniqueProperties, uniqueValue, tempSet, hasError, uniqueValueGroup, errors,
                            parallel);
                });
            }
        } finally {
            if (hasError.get()) {
                // 将temp里的数据从uniqueValueSet去掉
                if (!tempSet.isEmpty()) {
                    uniqueValueSet.removeAll(tempSet);
                    tempSet.clear();
                }
            } else {
                if (!CollectionUtils.isEmpty(tempSet)) {
                    String key = genValueSetKey();
                    this.getContext().addParameter(key, new HashSet<>(tempSet));
                }
            }
        }
    }

    private void validateConcurrent0(List<Property<?>> uniqueProperties, String uniqueValue, Set<String> tempSet,
                                     AtomicBoolean hasError, UniqueValueGroup uniqueValueGroup, ValidationErrors errors,
                                     boolean parallel) {
        if (StringUtils.isEmpty(uniqueValue)) {
            return;
        }

        if (hasError.get()) {
            return;
        }

        boolean succ = uniqueValueSet.add(uniqueValue);
        if (succ) {
            // 并发添加成功，加到temp，如果这一批数据有一个失败，则这些数据还需要从并发集合里移除
            tempSet.add(uniqueValue);
        } else {
            hasError.set(true);

            // 并发添加失败
            UniquePropertyValidation validation = this.getValidation();
            List<Object> objects = uniqueValueGroup.get(uniqueValue);
            List<ObjectError> objectErrors = objects.stream()
                    .map(dataObject -> validation.createUniquePropertyConcurrentError(dataObject, uniqueProperties))
                    .collect(Collectors.toList());

            if (parallel) {
                // 并行执行时，errors要确保线程安全
                synchronized (errors) {
                    errors.addErrors(objectErrors);
                }
            } else {
                errors.addErrors(objectErrors);
            }
        }
    }

    private void validateExist(List<Property<?>> uniqueProperties, Object[] dataObjects,
                               UniqueValueGroup uniqueValueGroup, ValidationErrors errors) {
        if (dataObjects.length <= 10) {
            // 数据少，逐行查询数量
            for (Object dataObject : dataObjects) {
                this.validateForCount(uniqueProperties, dataObject, errors, false);
            }
        } else {
            if (uniqueProperties.size() > 1) {
                // 多个uniqueProperties的批量查询和校验有点折腾,折中方案：并行执行
                Stream.of(dataObjects).parallel().forEach(dataObject -> {
                    this.validateForCount(uniqueProperties, dataObject, errors, true);
                });
            } else {
                // 大量数据，批查询比较检验，但是最多每次1000行
                int batchSize = 1000;

                if (dataObjects.length <= batchSize) {
                    this.validateForBatch(uniqueProperties.get(0), dataObjects, uniqueValueGroup, errors);
                } else {
                    List<Object[]> pages = PageUtils.getAllPageDatas(batchSize, dataObjects);

                    for (Object[] pageDataObjects : pages) {
                        this.validateForBatch(uniqueProperties.get(0), pageDataObjects, uniqueValueGroup, errors);
                    }
                }
            }
        }
    }

    private void validateForBatch(Property<?> property, Object[] dataObjects, UniqueValueGroup uniqueValueGroup,
                                  ValidationErrors errors) {
        UniquePropertyValidation validation = this.getValidation();
        QueryMeta queryMeta = this.getEntity().getQueryMeta();

        QueryInfo queryInfo = this.buildQueryInfoForBatch(queryMeta, property, dataObjects);
        if (!queryInfo.hasCriteria()) {
            return;
        }

        List<RowContract> rows = this.repository.queryForRows(queryMeta, queryInfo);
        Set<String> uniqueValuesFromDB = rows.stream().map(row -> String.valueOf(row.get(property.getAlias())))
                .collect(Collectors.toSet());

        if (CollectionUtils.isEmpty(uniqueValuesFromDB)) {
            return;
        }

        for (String uniqueValue : uniqueValueGroup.getUniqueValues()) {
            if (uniqueValuesFromDB.contains(uniqueValue)) {
                List<Object> objects = uniqueValueGroup.get(uniqueValue);

                for (Object dataObject : objects) {
                    errors.addError(
                            validation.createUniquePropertyExistError(dataObject, Collections.singletonList(property)));
                }
            }
        }
    }

    private void validateForCount(List<Property<?>> uniqueProperties, Object dataObject, ValidationErrors errors,
                                  boolean parallel) {
        UniquePropertyValidation validation = this.getValidation();
        QueryMeta queryMeta = this.getEntity().getQueryMeta();

        QueryInfo queryInfo = this.buildQueryInfoForCount(queryMeta, uniqueProperties, dataObject);
        if (!queryInfo.hasCriteria()) {
            return;
        }

        int count = this.repository.queryForRowCount(queryMeta, queryInfo);

        boolean exist = count > 0;
        if (exist) {
            ObjectError objectError = validation.createUniquePropertyExistError(dataObject, uniqueProperties);

            if (parallel) {
                synchronized (errors) {
                    errors.addError(objectError);
                }
            } else {
                errors.addError(objectError);
            }

        }
    }

    /**
     * 将业务对象按唯一值分组的结果
     *
     * @author hangwen
     */
    private static class UniqueValueGroup extends HashMap<String/* 值 */, List<Object>/* 对象 */> {

        private static final long serialVersionUID = 1L;

        /**
         * 获取唯一值的有序集合
         *
         * @return
         */
        String[] getUniqueValues() {
            return this.keySet().stream().filter(value -> value != null).sorted().toArray(s -> new String[s]);
        }
    }
}
